/*
 * Description:  Common Utils in windows
 */
#include "guest_utils.h"
#include <regex>
#include <string>
#include <typeinfo>
#include <stringapiset.h>
#include <timezoneapi.h>
#include <fstream>
#include <atlstr.h>
#include <CString>
#include <bcrypt.h>
#include <TlHelp32.h>
#include <iphlpapi.h>
#include <icmpapi.h>
#include <WtsApi32.h>
#include <UserEnv.h>
#include <sddl.h>
#include <Windows.h>
#include "log.h"
#include "error.h"
#include "net_utils.h"
#include "text_utils.h"
#include "../config/NetCard.h"
#include "session_utils.h"
#include "sstream"
#include <map>
#include "../performance/utils/registry_utils.h"

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "Ncrypt.lib")

namespace {
const int MAX_FILE_SIZE = 20 * 1024 * 1024; // 限定文件大小20MB
const int EXEC_CMD_TIMEOUT = 120000; // cmd超时时间: 2min
const std::string DEFAULT_DNS_ADDR = "223.5.5.5";
const std::string BACKUP_DNS_ADDR = "114.114.114.114";
const DWORD NO_ACTIVE_SESSION = 0xFFFFFFFF;
const int MAX_EXCLUSIONS_LENGTH = 2064; // windows代理排除项字段长度最大为2064
const std::string NetRegPath = "\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
const std::string RESOLUTION_REG_PATH = "System\\Resolutions";
const std::string WIDTH_REG_NAME = "width";
const std::string HEIGHT_REG_NAME = "height";
const uint32_t DEFAULT_3_2_WIDTH = 1920;
const uint32_t DEFAULT_3_2_HEIGHT = 1280;
const uint32_t DEFAULT_4_3_WIDTH = 1920;
const uint32_t DEFAULT_4_3_HEIGHT = 1440;
const uint32_t DEFAULT_103_67_WIDTH = 1854;
const uint32_t DEFAULT_103_67_HEIGHT = 1206;
}


namespace COMMON {

std::string GuestUtils::CalcHash(std::string &filePath)
{
    std::string result = "";
    HANDLE hFile = CreateFile(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        LOG_ERR("CreateFile go wrong: {}", GetLastError());
        return result;
    }
    DWORD dwFileSize = GetFileSize(hFile, 0);
    if (dwFileSize == INVALID_FILE_SIZE || dwFileSize > MAX_FILE_SIZE) {
        LOG_ERR("Invalid file: {}", filePath);
        CloseHandle(hFile);
        return result;
    }
    auto lpReadFileBuffer = std::vector<byte>(dwFileSize);
    DWORD lpReadNumberOfBytes;
    if (!ReadFile(hFile, &lpReadFileBuffer[0], dwFileSize, &lpReadNumberOfBytes, NULL)) {
        LOG_ERR("ReadFile go wrong: {}", GetLastError());
        CloseHandle(hFile);
        return result;
    }
    CloseHandle(hFile);

    BCRYPT_ALG_HANDLE       hAlg = NULL;
    BCRYPT_HASH_HANDLE      hHash = NULL;
    DWORD                   cbData = 0,
                            cbHash = 0;
    PBYTE                   pbHash = NULL;

    BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0);
    BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash, sizeof(DWORD), &cbData, 0);
    pbHash = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHash);
    if (pbHash == NULL) {
        LOG_ERR("Memory alloc failed");
        return result;
    }
    BCryptCreateHash(hAlg, &hHash, NULL, 0, NULL, 0, 0);
    BCryptHashData(hHash, &lpReadFileBuffer[0], dwFileSize, 0);
    BCryptFinishHash(hHash, pbHash, cbHash, 0);
    CHAR Temp[4] = {}; // sha256用64个十六进制字符表示，pbHash返回的每字节对应哈希结果的2个字符
    for (DWORD i = 0; i < cbHash; ++i) {
        if (sprintf_s(Temp, _countof(Temp), "%02x", pbHash[i]) == DEFAULT_ERROR) {
            LOG_ERR("Read hash failed");
            return result;
        }
        result.append(Temp);
    }
    BCryptDestroyHash(hHash);
    BCryptCloseAlgorithmProvider(hAlg, 0);
    HeapFree(GetProcessHeap(), 0, pbHash);
    return result;
}

bool GuestUtils::IsDirEmpty(std::string &filePath)
{
    WIN32_FIND_DATAA FindFileData;
    std::string dirPath = filePath + "*";
    HANDLE hFind = FindFirstFileA(dirPath.c_str(), &FindFileData);
    if (hFind == INVALID_HANDLE_VALUE) {
        LOG_ERR("Find firstfile failed: {}", GetLastError());
        return false;
    }
    bool isEmpty = true;
    do {
        if (strcmp(FindFileData.cFileName, ".") != 0 && strcmp(FindFileData.cFileName, "..") != 0) {
            isEmpty = false;
            break;
        }
    } while (FindNextFileA(hFind, &FindFileData) != 0);
    FindClose(hFind);
    return isEmpty;
}

bool GuestUtils::IsFirstSet(const std::string &flag)
{
    LOG_INFO("Check first {}", flag);
    HKEY regKey;
    bool res = false;
    auto keyResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\Setup"), 0, KEY_ALL_ACCESS, &regKey);
    if (keyResult != ERROR_SUCCESS) {
        LOG_ERR("Open registry failed: {}", keyResult);
    } else {
        DWORD regType = REG_DWORD;
        DWORD regSize = sizeof(DWORD);
        DWORD regValue = 0;
        auto valueResult = ::RegQueryValueEx(regKey, _T(flag.c_str()), 0, &regType, (LPBYTE)&regValue, &regSize);
        if (valueResult != ERROR_SUCCESS) {
            LOG_ERR("Query failed: {}", valueResult);
            ::RegCloseKey(regKey);
            return res;
        }
        LOG_INFO("get flag from RegQuery: {}", regValue);
        res = regValue ? true : false;
        ::RegCloseKey(regKey);
    }
    return res;
}

void GuestUtils::ResetFirst(const std::string &flag)
{
    LOG_INFO("Reset first {}", flag);
    HKEY regKey;
    auto keyResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\Setup"), 0, KEY_ALL_ACCESS, &regKey);
    if (keyResult != ERROR_SUCCESS) {
        LOG_ERR("Open registry failed: {}", keyResult);
    } else {
        DWORD regType = REG_DWORD;
        DWORD regSize = sizeof(DWORD);
        DWORD newValue = 0;
        auto valueResult = ::RegSetValueEx(regKey, _T(flag.c_str()), 0, regType, LPBYTE(&newValue), regSize);
        if (valueResult != ERROR_SUCCESS) {
            LOG_ERR("Change registry failed: {}", valueResult);
        } else {
            LOG_INFO("Change first flag to not");
        }
        ::RegCloseKey(regKey);
    }
}

bool GuestUtils::SetVmTimeZone(int timeZoneOffset)
{
    LOG_INFO("Set vm time zone begin {}", timeZoneOffset);
    if (!GuestUtils::AquriePri(SE_TIME_ZONE_NAME)) {
        LOG_ERR("Get priviledge failed");
        return false;
    }
    DWORD dwResult = 0;
    DWORD i = 0;
    DYNAMIC_TIME_ZONE_INFORMATION tzInfo;
    do {
        dwResult = EnumDynamicTimeZoneInformation(i++, &tzInfo);
        if (dwResult == ERROR_SUCCESS)
        {
            if (tzInfo.Bias == timeZoneOffset) {
                if (!SetDynamicTimeZoneInformation(&tzInfo)) {
                    LOG_ERR("Set vm time zone failed");
                    return false;
                }
                LOG_INFO("success to corrcet VM timeZone");
                return true;
            }
        }
    } while (dwResult != ERROR_NO_MORE_ITEMS);
    LOG_ERR("wrong timeZoneOffset value");
    return false;
}

bool GuestUtils::ExecCmd(const std::string &cmd, bool isUser, bool isTimeLimited)
{
    DWORD exitCode;
    LOG_INFO("ExecCmd: {}", cmd);
    return ExecCmdWithExitCode(cmd, isUser, exitCode, isTimeLimited);
}

bool GuestUtils::ExecCmdWithExitCode(const std::string &cmd, bool isUser, DWORD &exitCode, bool isTimeLimited)
{
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    GetStartupInfo(&si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
    PROCESS_INFORMATION pi;
    HANDLE hToken = nullptr;
    if (!CreateProcessCustomized(cmd, isUser, si, pi, hToken)) {
        return false;
    }
    DWORD dword = WaitForSingleObject(pi.hProcess, isTimeLimited ? EXEC_CMD_TIMEOUT : 0);
    if (dword != OK) {
        LOG_ERR("Failed to get exec {} result:{}", cmd.c_str(), dword);
    } else {
        if (!GetExitCodeProcess(pi.hProcess, &exitCode)) {
            LOG_ERR("Failed to get result: {}", GetLastError());
        } else {
            LOG_INFO("Exec result: {}", exitCode);
        }
    }
    if (isUser) {
        CloseHandle(hToken);
    }
    SAFE_CLOSE_HANDLE(pi.hProcess, nullptr);
    SAFE_CLOSE_HANDLE(pi.hThread, nullptr);
    return true;
}

int GuestUtils::ExecCmdResult(const std::string &cmd, bool isUser)
{
    DWORD exitCode;
    GuestUtils::ExecCmdWithExitCode(cmd, isUser, exitCode);
    LOG_INFO("ExecCmdResult: {}", exitCode);
    return exitCode;
}

bool GuestUtils::GenStartupInfo(HANDLE &handleRead, HANDLE &handleWrite, STARTUPINFO &startupInfo)
{
    SECURITY_ATTRIBUTES securityAttributes;
    securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
    securityAttributes.lpSecurityDescriptor = nullptr;
    securityAttributes.bInheritHandle = TRUE;
    if (CreatePipe(&handleRead, &handleWrite, &securityAttributes, 0) == FALSE) {
        return false;
    }
    startupInfo.cb = sizeof(STARTUPINFO);
    GetStartupInfo(&startupInfo);
    startupInfo.hStdError = handleWrite;
    startupInfo.hStdOutput = handleWrite;
    startupInfo.wShowWindow = SW_HIDE;
    startupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    return true;
}

bool GuestUtils::CreateProcessCustomized(const std::string &cmd, bool isUser, STARTUPINFO &si, PROCESS_INFORMATION &pi,
    HANDLE &hDupedToken)
{
    if (!isUser) {
        // transforming win32 BOOL(typedef int BOOL) to built-in C++ type bool
        return CreateProcess(NULL, const_cast<char*>(cmd.c_str()), NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi)
            ? true
            : false;
    }
    if (!AquriePri(SE_TCB_NAME)) {
        LOG_ERR("Get priviledge failed");
    }
    DupUserToken(hDupedToken);
    if (hDupedToken == nullptr) {
        LOG_ERR("Get token failed");
        return false;
    }
    LPVOID envBlock = nullptr;
    if (!CreateEnvironmentBlock(&envBlock, hDupedToken, false) || !envBlock) {
        LOG_ERR("Get environment failed");
        SAFE_CLOSE_HANDLE(hDupedToken, nullptr);
        return false;
    }
    if (!CreateProcessAsUser(hDupedToken, NULL, const_cast<char*>(cmd.c_str()), NULL, NULL,
        FALSE, CREATE_PRESERVE_CODE_AUTHZ_LEVEL | CREATE_UNICODE_ENVIRONMENT, envBlock, NULL, &si, &pi)) {
        LOG_ERR("Faild to excute cmd as user, error: {}", GetLastError());
        SAFE_CLOSE_HANDLE(hDupedToken, nullptr);
        DestroyEnvironmentBlock(envBlock);
        return false;
    }
    DestroyEnvironmentBlock(envBlock);
    return true;
}

bool GuestUtils::ExecCmdWithOutput(const std::string &cmd, bool isUser, std::string &output)
{
    LOG_DEBUG("enter PowershellUtils::SubProcess cmd = {}", cmd.c_str());
    output = "";
    HANDLE handleRead = nullptr;
    HANDLE handleWrite = nullptr;
    PROCESS_INFORMATION pi;
    SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    bool ret = true;
    HANDLE hDupedToken = nullptr;
    STARTUPINFO si;
    if (GenStartupInfo(handleRead, handleWrite, si)) {
        if (!CreateProcessCustomized(cmd, isUser, si, pi, hDupedToken)) {
            ret = false;
        }
        SAFE_CLOSE_HANDLE(handleWrite, nullptr);
        GetExecuteResult(handleRead, output);
    }
    SAFE_CLOSE_HANDLE(hDupedToken, nullptr);
    SAFE_CLOSE_HANDLE(handleWrite, nullptr);
    SAFE_CLOSE_HANDLE(handleRead, nullptr);
    SAFE_CLOSE_HANDLE(pi.hProcess, nullptr);
    SAFE_CLOSE_HANDLE(pi.hThread, nullptr);
    LOG_DEBUG("exit PowershellUtils::ExecuteCmd result = {}", ret);
    return ret;
}

void GuestUtils::GetExecuteResult(const HANDLE handleRead, std::string &retStr)
{
    LOG_DEBUG("enter PowershellUtils::GetExecuteResult");
    if (handleRead == nullptr) {
        return;
    }
    // 每次从管道中读取的最多字节数
    static const int READE_LINE_SIZE = 4096;
    char readBuff[READE_LINE_SIZE + 1] = {0};
    DWORD bytesRead = 0;
    int maxLoopTimes = 0;
    // 防止死循环
    static const int MAX_LOOP_TIMES = 100;
    do {
        maxLoopTimes++;
        auto result = ReadFile(handleRead, readBuff, READE_LINE_SIZE, &bytesRead, nullptr);
        if (result != TRUE) {
            break;
        }
        if (bytesRead > 0) {
            retStr += std::string(readBuff, bytesRead);
        }
        SecureZeroMemory(readBuff, sizeof(readBuff));
    } while (bytesRead > 0 && maxLoopTimes < MAX_LOOP_TIMES);
    LOG_INFO("[shuag PERF] - retStr: {}", retStr.c_str());
    LOG_DEBUG("exit PowershellUtils::GetExecuteResult");
}

bool GuestUtils::IsIpReachable(const std::string &ip)
{
    DWORD replySize = sizeof (ICMP_ECHO_REPLY) + 8; //8字节为ICMP错误消息
    LPVOID replyBuffer = (VOID *) malloc(replySize);
    if (replyBuffer == nullptr) {
        LOG_ERR("malloc failed");
        return false;
    }
    HANDLE hIcmpFile = IcmpCreateFile();
    if (hIcmpFile == INVALID_HANDLE_VALUE) {
        LOG_ERR("IcmpCreateFile failed: {}", GetLastError());
        free(replyBuffer);
        return false;
    }
    DWORD status = IcmpSendEcho2(hIcmpFile, NULL, NULL, NULL,
        inet_addr(ip.c_str()), NULL, 0, NULL, replyBuffer, replySize, 1000);
    IcmpCloseHandle(hIcmpFile);
    free(replyBuffer);
    if (!status) {
        LOG_ERR("Ping failed: {}", GetLastError());
        return false;
    }
    LOG_INFO("Ping success");
    return true;
}

bool GuestUtils::SetNetCard(const std::string &ip, const std::string &gateway, bool autoSyncHostDns)
{
    LOG_INFO("Set netcard");
    NetCard net;
    PIP_ADAPTER_INFO adapterInfo = new (std::nothrow) IP_ADAPTER_INFO();
    if (adapterInfo == nullptr) {
        LOG_ERR("AdapterInfo is NULL");
        return false;
    }
    unsigned long stSize = sizeof(IP_ADAPTER_INFO);
    GetAdaptersInfo(adapterInfo, &stSize);
    if (!TextUtils::CheckSqlInput(adapterInfo->AdapterName)) {
        delete adapterInfo;
        LOG_ERR("Bad adapter name");
        return false;
    }
    net.SetKey(adapterInfo->AdapterName);
    delete adapterInfo;
    if (!net.EnableDhcp()) {
        LOG_ERR("Failed to clean old setting");
    }
    if (!net.SetIpConfig(ip, std::string("255.255.255.0"))) {
        LOG_ERR("set ip failed");
        return false;
    }
    LOG_INFO("set ip succ");
    if (!net.SetGateway(gateway)) {
        LOG_ERR("set gateway failed");
        return false;
    }
    LOG_INFO("set gateway succ");
    bool dnsIsNull = false;
    if (!autoSyncHostDns || (NetUtils::DnsIsNull(dnsIsNull) && dnsIsNull)) {
        if (!net.SetDns(DEFAULT_DNS_ADDR, BACKUP_DNS_ADDR)) {
            LOG_ERR("set dns failed");
            return false;
        }
        LOG_INFO("set dns succ");
    }
    DWORD regValue = ReadCurrentUserFlag(NetRegPath, "ProxyEnable");
    LOG_INFO("Is proxy enable: {}", regValue);
    return true;
}

bool GuestUtils::SetProxy(const std::string& server, const std::string& exclusions)
{
    const std::string netReg = "\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
    // 设置代理启用状态
    bool proxyEnable = !server.empty();
    bool result = SetRegValueForCurrentUser(netReg, "ProxyEnable", REG_DWORD, proxyEnable ? "1" : "0");
    if (!result) {
        LOG_ERR("Failed to set ProxyEnable.");
        return false;
    }

    if (!proxyEnable) {
        LOG_INFO("Turn off proxy successfully.");
        return true;
    }

    // 设置代理服务器地址
    result = SetRegValueForCurrentUser(netReg, "ProxyServer", REG_SZ, TextUtils::UTF8ToGBK(server));
    if (!result) {
        LOG_ERR("Failed to set ProxyServer.");
        return false;
    }

    // 设置代理排除项
    do {
        // windows代理排除项字段长度最大为2064。
        if (exclusions.size() > MAX_EXCLUSIONS_LENGTH) {
            LOG_INFO("The size of exclusions > 2064");
            break;
        }
        result = SetRegValueForCurrentUser(netReg, "ProxyOverride", REG_SZ, TextUtils::UTF8ToGBK(exclusions));
        if (!result) {
            LOG_ERR("Failed to set ProxyOverride.");
            return false;
        }
    } while (false);
    LOG_INFO("Proxy settings updated successfully.");
    return true;
}

bool GuestUtils::SetDNS(const std::string& primaryDNS, const std::string& secondaryDNS)
{
    // 获取以太网接口ID
    std::string ethernetAdapterName;
    if (!NetUtils::GetEthernetAdapterName(ethernetAdapterName)) {
        LOG_ERR("Failed to get ethernet adapter name.");
        return false;
    }
    if (!TextUtils::CheckGuid(ethernetAdapterName)) {
        LOG_ERR("Ethernet adapter name is invaild guid.");
        return false;
    }
    
    // 以太网接口注册表路径
    std::string regPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\" + ethernetAdapterName;

    LOG_INFO("[GuestUtils - SetDNS]regPath: {}, primaryDNS: {}, secondaryDNS: {}", regPath, primaryDNS, secondaryDNS);
    // 打开注册表键
    HKEY hKey;
    LONG result = RegOpenKeyExA(HKEY_LOCAL_MACHINE, regPath.c_str(), 0, KEY_WRITE, &hKey);
    if (result != ERROR_SUCCESS) {
        LOG_ERR("Failed to open registry key. {}", result);
        return false;
    }

    // 设置DNS地址
    std::string dns = primaryDNS + "," + secondaryDNS;
    result = RegSetValueExA(hKey, "NameServer", 0, REG_SZ, (const BYTE*)dns.c_str(), dns.size());
    if (result != ERROR_SUCCESS) {
        LOG_ERR("Failed to set DNS. {}", result);
        RegCloseKey(hKey);
        return false;
    }

    // 关闭注册表键
    RegCloseKey(hKey);
    LOG_INFO("The DNS is set successfully.");
    return true;
}

void GuestUtils::NetMap(const std::string &gateway)
{
    std::string shareFolderCmd = "net use Z: /del /y";
    if (!GuestUtils::ExecCmd(shareFolderCmd, true)) {
        LOG_ERR("Failed to clean net mapping");
    }
    LOG_INFO("Delete Z");
    shareFolderCmd = "net use Z: \\\\" + gateway + "\\DesktopAppEngineShared /p:n";
    if (!GuestUtils::ExecCmd(shareFolderCmd, true)) {
        LOG_ERR("Failed to excute net mapping");
    }
    LOG_INFO("Connect Z");
    return;
}

void GuestUtils::InitDisplay(uint32_t surfaceWidth, uint32_t surfaceHeight)
{
    std::string flag = "FirstEnabled2D";
    if (GuestUtils::IsFirstSet(flag)) {
        GuestUtils::ModifyResolution(surfaceWidth, surfaceHeight, false);
        GuestUtils::ResetFirst(flag);
        LOG_INFO("Success to execute display init");
        return;
    }
    LOG_INFO("Display init has been executed");
    return;
}

bool GuestUtils::ModifyResolution(uint32_t surfaceWidth, uint32_t surfaceHeight, bool isInstall)
{
    std::pair<uint32_t, uint32_t> resolution = GuestUtils::GetTargetResolution(surfaceWidth, surfaceHeight, isInstall);
    LOG_INFO("Modify resolution begin, targetWidth is {}, targetHeight is {}", resolution.first, resolution.second);
    std::string displayCmd = "C:\\ProgramData\\display\\display.exe " + std::to_string(resolution.first) + " " +
                             std::to_string(resolution.second);
    DWORD modifyResult = GuestUtils::ExecCmdResult(displayCmd, true);
    if (modifyResult != 0) {
        LOG_ERR("display.exe failed to modify resolution, result is {}", modifyResult);
        return false;
    }
    return true;
}

std::pair<uint32_t, uint32_t> GuestUtils::GetTargetResolution(uint32_t surfaceWidth, uint32_t surfaceHeight,
                                                              bool isInstall)
{
    uint32_t targetWidth = 0;
    uint32_t targetHeight = 0;
    if (surfaceWidth == 0 || surfaceHeight == 0) {
        LOG_ERR("Invalid surface parameters");
        return std::make_pair(targetWidth, targetHeight);
    }
    std::string resolutionFlag = "";
    if (surfaceWidth * DEFAULT_103_67_HEIGHT / DEFAULT_103_67_WIDTH == surfaceHeight
        || surfaceWidth * DEFAULT_103_67_WIDTH / DEFAULT_103_67_HEIGHT == surfaceHeight) {
        resolutionFlag = "_103_67";
        targetWidth = DEFAULT_103_67_WIDTH;
        targetHeight = DEFAULT_103_67_HEIGHT;
    } else if (surfaceWidth * DEFAULT_4_3_HEIGHT / DEFAULT_4_3_WIDTH == surfaceHeight
        || surfaceWidth * DEFAULT_4_3_WIDTH / DEFAULT_4_3_HEIGHT == surfaceHeight) {
        resolutionFlag = "_4_3";
        targetWidth = DEFAULT_4_3_WIDTH;
        targetHeight = DEFAULT_4_3_HEIGHT;
    } else if (surfaceWidth * DEFAULT_3_2_HEIGHT / DEFAULT_3_2_WIDTH == surfaceHeight
        || surfaceWidth * DEFAULT_3_2_WIDTH / DEFAULT_3_2_HEIGHT == surfaceHeight) {
        resolutionFlag = "_3_2";
        targetWidth = DEFAULT_3_2_WIDTH;
        targetHeight = DEFAULT_3_2_HEIGHT;
    } else {
        LOG_WARN("Surface parameters unknown");
    }

    DWORD storedWidth = 0;
    DWORD storedHeight = 0;
    if (!isInstall && REGISTRY_UTILS.QueryRegDWORD(
        HKEY_LOCAL_MACHINE, RESOLUTION_REG_PATH.c_str(), (WIDTH_REG_NAME + resolutionFlag).c_str(), storedWidth)
        && REGISTRY_UTILS.QueryRegDWORD(HKEY_LOCAL_MACHINE, RESOLUTION_REG_PATH.c_str(),
        (HEIGHT_REG_NAME + resolutionFlag).c_str(), storedHeight)) {
        LOG_INFO("storedWidth is {}, storedHeight is {}", storedWidth, storedHeight);
        if (storedWidth != 0 && storedHeight != 0) {
            targetWidth = static_cast<uint32_t>(storedWidth);
            targetHeight = static_cast<uint32_t>(storedHeight);
        }
    } else {
        LOG_ERR("query stored resolution failed");
    }

    return std::make_pair(targetWidth, targetHeight);
}

bool GuestUtils::AquriePri(LPCTSTR privilegeName)
{
    HANDLE processHandle = GetCurrentProcess();
    HANDLE tokenHandle = NULL;
    if (!OpenProcessToken(processHandle, TOKEN_ADJUST_PRIVILEGES, &tokenHandle)) {
        LOG_ERR("OpenProcessToken failed: {}", GetLastError());
        return false;
    }
    LUID luidDebugLUID;
    if (!LookupPrivilegeValue(NULL, privilegeName, &luidDebugLUID)) {
        LOG_ERR("LookupPrivilegeValue failed: {}", GetLastError());
        CloseHandle(tokenHandle);
        return false;
    }
    TOKEN_PRIVILEGES newToken;
    newToken.PrivilegeCount = 1;
    newToken.Privileges[0].Luid = luidDebugLUID;
    newToken.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if (!AdjustTokenPrivileges(tokenHandle, FALSE, &newToken, sizeof(newToken), NULL, NULL)) {
        LOG_ERR("AdjustTokenPrivileges failed: {}", GetLastError());
        CloseHandle(tokenHandle);
        return false;
    }
    CloseHandle(tokenHandle);
    return true;
}

bool GuestUtils::DupUserToken(HANDLE &newToken)
{
    HANDLE hToken = nullptr;
    if (!SessionUtils::IsUserLogon()) {
        LOG_ERR("get token failed because no user logon");
        return false;
    }
    if (!WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken)) {
        LOG_ERR("failed to get token :{}", GetLastError());
        return false;
    }
    if (hToken == nullptr) {
        LOG_ERR("invalid token");
        return false;
    }
    BOOL ret = DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nullptr, SecurityImpersonation, TokenPrimary, &newToken);
    CloseHandle(hToken);
    if (!ret) {
        LOG_ERR("DuplicateTokenEx Failed:{}", GetLastError());
        return false;
    }
    return true;
}

bool GuestUtils::SetVmTime(SYSTEMTIME &systemTime)
{
    LOG_INFO("Set Vm Time begin");
    if (!GuestUtils::AquriePri(SE_SYSTEMTIME_NAME)) {
        LOG_ERR("Get priviledge failed");
        return false;
    }
    if (!SetLocalTime(&systemTime)) {
        LOG_ERR("fail to corrcet VM time");
        return false;
    }
    LOG_INFO("success to corrcet VM time");
    return true;
}

std::string GuestUtils::GetLastAvailableLetter()
{
    std::stringstream rootPath;
    for (char i = 'Z'; i >= 'A'; i--) {
        rootPath.str("");
        rootPath << i << ":";
        UINT type = GetDriveType(rootPath.str().c_str());
        if (type > DRIVE_NO_ROOT_DIR) {
            continue;
        }
        std::stringstream cmd;
        cmd << "cmd.exe /C if exist " << rootPath.str() << "\\ (EXIT /B 0) else (EXIT /B 1)";
        DWORD exitCode;
        GuestUtils::ExecCmdWithExitCode(cmd.str(), true, exitCode);
        if (exitCode != 0) {
            LOG_INFO("find available driver: {}", rootPath.str());
            return rootPath.str();
        }
    }
    return "";
}

std::string GuestUtils::GetCurrentUserRegKey()
{
    HANDLE hToken = nullptr;
    DupUserToken(hToken);
    if (hToken == nullptr) {
        LOG_ERR("Get token failed");
        return "";
    }
    DWORD dwLength = 0;
    GetTokenInformation(hToken, TokenUser, nullptr, 0, &dwLength);
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
        LOG_ERR("Failed to get token information length: {}", GetLastError());
        CloseHandle(hToken);
        return "";
    }
    PTOKEN_USER pTokenUser = static_cast<PTOKEN_USER>(malloc(dwLength));
    if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwLength, &dwLength)) {
        LOG_ERR("Failed to get token information: {}", GetLastError());
        CloseHandle(hToken);
        free(pTokenUser);
        return "";
    }
    LPTSTR pStringSid = nullptr;
    auto ret = ConvertSidToStringSid(pTokenUser->User.Sid, &pStringSid);
    free(pTokenUser);
    CloseHandle(hToken);
    if (!ret) {
        LOG_ERR("Failed to convert SID to string: {}", GetLastError());
        return "";
    }

    std::string sid(pStringSid);
    return sid;
}

DWORD GuestUtils::ReadCurrentUserFlag(const std::string &root, const std::string &name)
{
    HKEY regKey;
    DWORD regValue = 0;
    std::string regRoot = GetCurrentUserRegKey();
    auto keyResult = RegOpenKeyEx(HKEY_USERS, _T((regRoot + root).c_str()), 0, KEY_ALL_ACCESS, &regKey);
    if (keyResult != ERROR_SUCCESS) {
        LOG_ERR("Open registry failed: {}", keyResult);
    } else {
        DWORD regType = REG_DWORD;
        DWORD regSize = sizeof(DWORD);
        auto valueResult = ::RegQueryValueEx(regKey, _T(name.c_str()), 0, &regType, (LPBYTE)&regValue, &regSize);
        if (valueResult != ERROR_SUCCESS) {
            LOG_ERR("Query failed: {}", valueResult);
            ::RegCloseKey(regKey);
            return regValue;
        }
        LOG_INFO("get flag from RegQuery: {}", regValue);
        ::RegCloseKey(regKey);
    }
    return regValue;
}

static inline bool SafeStoull(const std::string &str, unsigned long long &value)
{
    try {
        value = std::stoull(str);
    } catch (std::exception &e) {
        return false;
    }
    return true;
}

static inline bool SafeStoul(const std::string str, unsigned long &value)
{
    try {
        value = std::stoul(str);
    } catch (std::exception &e) {
        return false;
    }
    return true;
}

bool GuestUtils::SetRegValueForCurrentUser(
    const std::string &root, const std::string &name, const DWORD dwType, const std::string &value)
{
    LOG_INFO("[GuestUtils-SetRegValueForCurrentUser]root:{},name:{},dwtype:{},value:{}", root, name, dwType, value);
    HKEY hKey;
    std::string regRoot = GetCurrentUserRegKey();
    if (RegCreateKeyExA(HKEY_USERS, (regRoot + root).c_str(), 0, NULL, REG_OPTION_NON_VOLATILE,
                        KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS) {
        LOG_ERR("Failed to create registry key. {}", GetLastError());
        return false;
    }
    DWORD cbData = 0;
    if (dwType == REG_DWORD || dwType == REG_DWORD_LITTLE_ENDIAN || dwType == REG_DWORD_BIG_ENDIAN) {
        cbData = sizeof(DWORD);
        DWORD vauleData;
        bool ret = SafeStoul(value, vauleData);
        if (!ret ||
            RegSetValueExA(hKey, name.c_str(), 0, dwType, reinterpret_cast<const BYTE *>(&vauleData), cbData) !=
            ERROR_SUCCESS) {
            LOG_ERR("Failed to set registry value. {}", GetLastError());
            RegCloseKey(hKey);
            return false;
        }
    } else if (dwType == REG_QWORD || dwType == REG_QWORD_LITTLE_ENDIAN) {
        cbData = sizeof(ULONGLONG);
        ULONGLONG vauleData;
        bool ret = SafeStoull(value, vauleData);
        if (!ret ||
            RegSetValueExA(hKey, name.c_str(), 0, dwType, reinterpret_cast<const BYTE *>(&vauleData), cbData) !=
            ERROR_SUCCESS) {
            LOG_ERR("Failed to set registry value. {}", GetLastError());
            RegCloseKey(hKey);
            return false;
        }
    } else if (dwType == REG_BINARY || dwType == REG_EXPAND_SZ || dwType == REG_LINK || dwType == REG_MULTI_SZ ||
               dwType == REG_SZ) {
        cbData = value.size();
        if (RegSetValueExA(hKey, name.c_str(), 0, dwType, reinterpret_cast<const BYTE *>(value.c_str()), cbData) !=
            ERROR_SUCCESS) {
            LOG_ERR("Failed to set registry value. {}", GetLastError());
            RegCloseKey(hKey);
            return false;
        }
    } else {
        LOG_ERR("Failed to set registry value, invalid dwType {}", dwType);
        RegCloseKey(hKey);
        return false;
    }
    LOG_INFO("Registry value set successfully. {}", GetLastError());
    RegCloseKey(hKey);
    return true;
}

int GuestUtils::GetAvailableLetterCount()
{
    int count = 0;
    std::stringstream letter;
    for (char i = 'Z'; i >= 'A'; i--) {
        letter.str("");
        letter << i << ":";
        UINT type = GetDriveType(letter.str().c_str());
        if (type > DRIVE_NO_ROOT_DIR) {
            continue;
        }
        std::stringstream cmd;
        cmd << "cmd.exe /C if exist " << letter.str() << "\\ (EXIT /B 0) else (EXIT /B 1)";
        DWORD exitCode;
        GuestUtils::ExecCmdWithExitCode(cmd.str(), true, exitCode);
        if (exitCode != 0) {
            count++;
        }
    }
    return count;
}

DWORD GuestUtils::GetServiceState(const std::string &serviceName)
{
    SC_HANDLE hSCManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
    if (hSCManager == nullptr) {
        LOG_ERR("Failed to open service control manager");
        return -1;
    }

    SC_HANDLE hService = OpenService(hSCManager, serviceName.c_str(), SERVICE_QUERY_STATUS);
    if (hService == nullptr) {
        LOG_ERR("Failed to open service");
        CloseServiceHandle(hSCManager);
        return -1;
    }

    SERVICE_STATUS serviceStatus;
    if (QueryServiceStatus(hService, &serviceStatus) == 0) {
        LOG_ERR("Failed to query service status");
        CloseServiceHandle(hService);
        CloseServiceHandle(hSCManager);
        return -1;
    }
    DWORD state = serviceStatus.dwCurrentState;

    CloseServiceHandle(hService);
    CloseServiceHandle(hSCManager);
    return state;
}

bool GuestUtils::GetCurrentUsername(std::string& username)
{
    DWORD currentSessionId = WTSGetActiveConsoleSessionId();
    if (currentSessionId == NO_ACTIVE_SESSION) {
        LOG_ERR("get username failed, no user logon");
        return false;
    }
    return GetCurrentUserNameBySessionId(username, currentSessionId);
}

bool GuestUtils::GetCurrentUserNameBySessionId(std::string& username, DWORD sessionId)
{
    WTS_INFO_CLASS wtsInfoClass = WTS_INFO_CLASS::WTSUserName;
    char* activeUsername = nullptr;
    DWORD byteSize = 0;
    if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
        sessionId, wtsInfoClass, &activeUsername, &byteSize)) {
        if (activeUsername != nullptr) {
            LOG_INFO("Get username success");
            username = std::string(activeUsername);
            WTSFreeMemory(activeUsername);
            return true;
        } else {
            LOG_ERR("Session is belong to system");
            return false;
        }
    } else {
        LOG_ERR("Get info failed: {}", GetLastError());
        return false;
    }
}

bool GuestUtils::IsProcessRunning(const std::string& process_name)
{
    PROCESSENTRY32 pe;
    pe.dwSize = sizeof(PROCESSENTRY32);

    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        return false;
    }
    bool found = false;
    Process32First(hProcessSnap, &pe);
    do {
        if (pe.szExeFile == process_name) {
            found = true;
            break;
        }
    } while (Process32Next(hProcessSnap, &pe));

    CloseHandle(hProcessSnap);
    return found;
}

bool GuestUtils::W32timeSet()
{
    HKEY regKey;
    auto keyResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\w32time\\Config"),
        0, KEY_SET_VALUE, &regKey);
    if (keyResult != ERROR_SUCCESS) {
        LOG_ERR("Open registry failed: {}", keyResult);
        return false;
    } else {
        DWORD regType = REG_DWORD;
        DWORD regSize = sizeof(DWORD);
        DWORD newValue = 0;
        auto valueResult = ::RegSetValueEx(regKey, _T("UtilizeSslTimeData"), 0, regType, LPBYTE(&newValue), regSize);
        if (valueResult != ERROR_SUCCESS) {
            LOG_ERR("Change registry failed: {}", valueResult);
            ::RegCloseKey(regKey);
            return false;
        }
        ::RegCloseKey(regKey);
    }
    return true;
}

bool GuestUtils::CreateUserProcess(const std::string &cmd, STARTUPINFO &si, PROCESS_INFORMATION &pi)
{
    HANDLE hToken = nullptr;
    bool ret = CreateProcessCustomized(cmd, true, si, pi, hToken);
    SAFE_CLOSE_HANDLE(hToken, nullptr);
    return ret;
}

bool GuestUtils::SetHostDomainAccountAndSN(const std::string& domainAccountName, const std::string& sn)
{
    LOG_INFO("[GuestUtils-SetHostDomainAccountAndSN]SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\VMHost"
        ", item1:Host_Account, item2:Host_Serial_Number");
    HKEY regKey;
    auto keyResult = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
        _T("SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\VMHost"),
        0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &regKey, NULL);
    if (keyResult != ERROR_SUCCESS) {
        LOG_ERR("Open registry failed: {}", keyResult);
        return false;
    }
    auto accountRet = ::RegSetValueEx(regKey, _T("Host_Account"), 0, REG_SZ,
        reinterpret_cast<const BYTE *>(domainAccountName.c_str()), domainAccountName.size());
    auto snRet = ::RegSetValueEx(regKey, _T("Host_Serial_Number"), 0, REG_SZ,
        reinterpret_cast<const BYTE *>(sn.c_str()), sn.size());
    if (accountRet != ERROR_SUCCESS || snRet != ERROR_SUCCESS) {
        LOG_ERR("Set host account or sn failed");
        ::RegCloseKey(regKey);
        return false;
    }
    ::RegCloseKey(regKey);
    return true;
}
}